package com.diy.sigmund.diycommon.spring.config.dynamicthreadpool;

import com.diy.sigmund.diycommon.entity.constant.LogConstant;
import com.sun.management.OperatingSystemMXBean;
import java.lang.management.ManagementFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import javax.annotation.PostConstruct;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * @author ylm-sigmund
 * @since 2023/1/29 22:31
 */
@Component
public class DynamicThreadPoolManager {

    private static final Logger log = LoggerFactory.getLogger(DynamicThreadPoolManager.class);

    @Autowired
    private DynamicThreadPoolProperties dynamicThreadPoolProperties;

    /**
     * 存储线程池对象
     */
    private final Map<String, ThreadPoolMonitor> threadPoolExecutorMap = new LinkedHashMap<>();


    public Map<String, ThreadPoolMonitor> getThreadPoolExecutorMap() {
        return threadPoolExecutorMap;
    }


    /**
     * 初始化线程池
     */
    @PostConstruct
    public void init() {
        createThreadPools(dynamicThreadPoolProperties);
    }

    /**
     * 初始化线程池的创建
     */
    private void createThreadPools(DynamicThreadPoolProperties dynamicThreadPoolProperties) {
        // queues == 0 ? new SynchronousQueue<Runnable>() :
        //         (queues < 0 ? new MemorySafeLinkedBlockingQueue<Runnable>()
        //                 : new LinkedBlockingQueue<Runnable>(queues))
        dynamicThreadPoolProperties.getExecutors().forEach(config -> {
            threadPoolExecutorMap.putIfAbsent(config.getThreadPoolName(), new ThreadPoolMonitor(
                    config.getCorePoolSize(),
                    config.getMaximumPoolSize(),
                    config.getKeepAliveTime(),
                    config.getUnit(),
                    new ResizableCapacityLinkedBlockingQueue<>(config.getQueueCapacity()),
                    RejectedExecutionHandlerEnum.getRejectedExecutionHandler(config.getRejectedExecutionType()),
                    config.getThreadPoolName()
            ));
        });
        Map<String, Object> metricMap = getDynamicThreadPoolMetricMap();
        log.info(LogConstant.SPRING_LOG, "动态线程池初始化完成" + metricMap);
    }

    @NotNull
    public Map<String, Object> getDynamicThreadPoolMetricMap() {
        Map<String, Object> metricMap = new LinkedHashMap<>();
        List<Map> threadPools = new ArrayList<>();
        threadPoolExecutorMap.forEach((k, v) -> {
            ThreadPoolMonitor threadPoolMonitor = v;
            Map<String, Object> poolInfo = new LinkedHashMap<>();
            poolInfo.put("thread.pool.name", k);
            poolInfo.put("thread.pool.corePoolSize", threadPoolMonitor.getCorePoolSize());
            poolInfo.put("thread.pool.maximumPoolSize", threadPoolMonitor.getMaximumPoolSize());
            poolInfo.put("thread.pool.largestPoolSize", threadPoolMonitor.getLargestPoolSize());
            poolInfo.put("thread.pool.poolSize", threadPoolMonitor.getPoolSize());
            poolInfo.put("thread.pool.activeCount", threadPoolMonitor.getActiveCount());
            poolInfo.put("thread.pool.completedTaskCount", threadPoolMonitor.getCompletedTaskCount());
            poolInfo.put("thread.pool.taskCount", threadPoolMonitor.getTaskCount());
            poolInfo.put("thread.pool.minCostTime", threadPoolMonitor.getMinCostTime());
            poolInfo.put("thread.pool.maxCostTime", threadPoolMonitor.getMaxCostTime());
            poolInfo.put("thread.pool.averageCostTime", threadPoolMonitor.getAverageCostTime());
            ResizableCapacityLinkedBlockingQueue<Runnable> queue = (ResizableCapacityLinkedBlockingQueue) threadPoolMonitor.getQueue();
            poolInfo.put("thread.pool.queue.name", queue.getClass().getName());
            poolInfo.put("thread.pool.queue.capacity", queue.getCapacity());
            poolInfo.put("thread.pool.queue.size", queue.size());
            poolInfo.put("thread.pool.queue.remainingCapacity", queue.remainingCapacity());
            poolInfo.put("thread.pool.rejected.name", threadPoolMonitor.getRejectedExecutionHandler().getClass().getName());
            MonitorThreadFactory monitorThreadFactory = (MonitorThreadFactory) threadPoolMonitor.getThreadFactory();
            poolInfo.put("thread.pool.uncaughtExceptionCount", monitorThreadFactory.getUncaughtExceptionCount());
            poolInfo.put("Runtime.maxMemory", Runtime.getRuntime().maxMemory());
            final long gb = 1024 * 1024 * 1024;
            final String gbUnit = "GB";
            final long mb = 1024 * 1024;
            final String mbUnit = "MB";
            long totalMemory = Runtime.getRuntime().totalMemory();
            poolInfo.put("Runtime.totalMemory",
                    totalMemory / gb != 0 ? (totalMemory / gb + gbUnit) : (totalMemory / mb + mbUnit));
            long freeMemory = Runtime.getRuntime().freeMemory();
            poolInfo.put("Runtime.freeMemory", freeMemory / gb != 0 ? (freeMemory / gb + gbUnit) : (freeMemory / mb + mbUnit));
            double memoryUsage = (double) (totalMemory - freeMemory) / (double) totalMemory;
            poolInfo.put("Runtime.memoryUsage", new DecimalFormat("0.000%").format(memoryUsage));

            OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
            double systemCpuLoad = operatingSystemMXBean.getSystemCpuLoad();
            double processCpuLoad = operatingSystemMXBean.getProcessCpuLoad();
            poolInfo.put("ManagementFactory.OperatingSystemMXBean.systemCpuLoad",
                    new DecimalFormat("0.000%").format(systemCpuLoad));
            poolInfo.put("ManagementFactory.OperatingSystemMXBean.processCpuLoad",
                    new DecimalFormat("0.000%").format(processCpuLoad));
            threadPools.add(poolInfo);
        });
        metricMap.put("threadPools", threadPools);
        return metricMap;
    }

    /**
     * 调整线程池
     */
    private void changeThreadPools(DynamicThreadPoolProperties dynamicThreadPoolProperties) {
        dynamicThreadPoolProperties.getExecutors().forEach(config -> {
            ThreadPoolExecutor threadPoolExecutor = threadPoolExecutorMap.get(config.getThreadPoolName());
            if (Objects.nonNull(threadPoolExecutor)) {
                threadPoolExecutor.setCorePoolSize(config.getCorePoolSize());
                threadPoolExecutor.setMaximumPoolSize(config.getMaximumPoolSize());
                threadPoolExecutor.setKeepAliveTime(config.getKeepAliveTime(), config.getUnit());
                threadPoolExecutor.setRejectedExecutionHandler(
                        RejectedExecutionHandlerEnum.getRejectedExecutionHandler(config.getRejectedExecutionType()));
                BlockingQueue<Runnable> queue = threadPoolExecutor.getQueue();
                if (queue instanceof ResizableCapacityLinkedBlockingQueue) {
                    ((ResizableCapacityLinkedBlockingQueue<Runnable>) queue).setCapacity(config.getQueueCapacity());
                }
            }
        });
    }


    /**
     * @see ContextRefresher#refresh()
     */
    @EventListener
    public void envListener(EnvironmentChangeEvent event) {
        log.info("配置发生变更" + event);
        changeThreadPools(dynamicThreadPoolProperties);
    }

}

